# -*- coding: utf-8 -*-
"""
菜单服务
"""

from typing import List, Dict, Any, Optional
from sqlalchemy.ext.asyncio import AsyncSession
from sqlalchemy.orm import selectinload
from sqlalchemy import select, delete, and_, or_, func
from app.models.menu import Menu
from app.models.user import Role, Permission
from app.schemas.menu import MenuCreate, MenuUpdate, MenuQuery
from app.core.exceptions import BusinessException


class MenuService:
    """菜单服务类"""

    async def create(self, db: AsyncSession, menu_data: MenuCreate, tenant_id: str, user_id: int) -> Menu:
        """创建菜单"""
        # 检查菜单名称是否重复
        existing = await self.get_by_name(db, menu_data.name, tenant_id)
        if existing:
            raise BusinessException("菜单名称已存在")

        # 如果指定了父菜单，检查父菜单是否存在
        if menu_data.parent_id is not None and menu_data.parent_id > 0:
            parent = await self.get_by_id(db, menu_data.parent_id, tenant_id)
            if not parent:
                raise BusinessException("父菜单不存在")

        # 创建菜单
        menu = Menu(
            **menu_data.model_dump(),
            tenant_id=tenant_id,
            created_by=user_id,
            updated_by=user_id
        )
        db.add(menu)
        await db.commit()
        await db.refresh(menu)
        return menu

    async def update(self, db: AsyncSession, menu_id: int, menu_data: MenuUpdate, tenant_id: str, user_id: int) -> Menu:
        """更新菜单"""
        menu = await self.get_by_id(db, menu_id, tenant_id)
        if not menu:
            raise BusinessException("菜单不存在")

        # 检查菜单名称是否重复（排除自身）
        if menu_data.name:
            existing = await self.get_by_name(db, menu_data.name, tenant_id)
            if existing and existing.id != menu_id:
                raise BusinessException("菜单名称已存在")

        # 检查父菜单是否有效
        if menu_data.parent_id is not None and menu_data.parent_id > 0:
            if menu_data.parent_id == menu_id:
                raise BusinessException("不能将自己设为父菜单")

            parent = await self.get_by_id(db, menu_data.parent_id, tenant_id)
            if not parent:
                raise BusinessException("父菜单不存在")

            # 检查是否会形成循环引用
            if await self._has_circular_reference(db, menu_id, menu_data.parent_id, tenant_id):
                raise BusinessException("会形成循环引用")

        # 更新菜单
        for field, value in menu_data.model_dump(exclude_unset=True).items():
            setattr(menu, field, value)
        menu.updated_by = user_id

        await db.commit()
        await db.refresh(menu)
        return menu

    async def delete(self, db: AsyncSession, menu_id: int, tenant_id: str) -> bool:
        """删除菜单"""
        menu = await self.get_by_id(db, menu_id, tenant_id)
        if not menu:
            raise BusinessException("菜单不存在")

        # 检查是否有子菜单
        children = await self.get_children(db, menu_id, tenant_id)
        if children:
            raise BusinessException("请先删除子菜单")

        # 删除菜单
        await db.execute(
            delete(Menu).where(
                and_(Menu.id == menu_id, Menu.tenant_id == tenant_id)
            )
        )
        await db.commit()
        return True

    async def get_by_id(self, db: AsyncSession, menu_id: int, tenant_id: str) -> Optional[Menu]:
        """根据ID获取菜单"""
        result = await db.execute(
            select(Menu)
            .where(and_(Menu.id == menu_id, Menu.tenant_id == tenant_id))
            .options(selectinload(Menu.permission))
        )
        return result.scalar_one_or_none()

    async def get_by_name(self, db: AsyncSession, name: str, tenant_id: str) -> Optional[Menu]:
        """根据名称获取菜单"""
        result = await db.execute(
            select(Menu).where(
                and_(Menu.name == name, Menu.tenant_id == tenant_id)
            )
        )
        return result.scalar_one_or_none()

    async def get_children(self, db: AsyncSession, parent_id: int, tenant_id: str) -> List[Menu]:
        """获取子菜单"""
        result = await db.execute(
            select(Menu)
            .where(and_(Menu.parent_id == parent_id, Menu.tenant_id == tenant_id))
            .order_by(Menu.sort, Menu.id)
        )
        return result.scalars().all()

    async def get_list(self, db: AsyncSession, query: MenuQuery, tenant_id: str,
                       skip: int = 0, limit: int = 100) -> List[Menu]:
        """获取菜单列表"""
        stmt = select(Menu).where(Menu.tenant_id == tenant_id)

        if query.name:
            stmt = stmt.where(Menu.name.like(f"%{query.name}%"))
        if query.title:
            stmt = stmt.where(Menu.title.like(f"%{query.title}%"))
        if query.type is not None:
            stmt = stmt.where(Menu.type == query.type)
        if query.status is not None:
            stmt = stmt.where(Menu.status == query.status)
        if query.visible is not None:
            stmt = stmt.where(Menu.visible == query.visible)

        stmt = stmt.order_by(Menu.sort, Menu.id).offset(skip).limit(limit)
        result = await db.execute(stmt)
        return result.scalars().all()

    async def get_tree(self, db: AsyncSession, tenant_id: str, parent_id: Optional[int] = None) -> List[Dict[str, Any]]:
        """获取菜单树"""
        # 获取所有菜单
        result = await db.execute(
            select(Menu)
            .where(and_(Menu.tenant_id == tenant_id, Menu.status == True))
            .order_by(Menu.sort, Menu.id)
        )
        all_menus = result.scalars().all()

        # 构建菜单树
        return self._build_menu_tree(all_menus, parent_id)

    async def get_user_menu_tree(self, db: AsyncSession, user_id: int, tenant_id: str) -> List[Dict[str, Any]]:
        """获取用户的菜单树（基于权限）"""
        # 获取用户的所有角色权限
        from app.services.user import user_service
        permissions = await user_service.get_user_permissions(db, user_id, tenant_id)

        if not permissions:
            return []

        # 查询用户有权限的菜单
        result = await db.execute(
            select(Menu)
            .where(and_(
                Menu.tenant_id == tenant_id,
                Menu.status == True,
                Menu.visible == True,
                or_(
                    Menu.permission_code.in_(permissions),
                    Menu.permission_code.is_(None),  # 公共菜单
                    Menu.type == 'M'  # 目录通常都显示
                )
            ))
            .order_by(Menu.sort, Menu.id)
        )
        user_menus = result.scalars().all()

        return self._build_menu_tree(user_menus, None)

    def _build_menu_tree(self, menus: List[Menu], parent_id: Optional[int] = None) -> List[Dict[str, Any]]:
        """构建菜单树结构"""
        tree = []
        for menu in menus:
            if menu.parent_id == parent_id:
                menu_dict = self._menu_to_dict(menu)
                children = self._build_menu_tree(menus, menu.id)
                if children:
                    menu_dict['children'] = children
                tree.append(menu_dict)
        return tree

    def _menu_to_dict(self, menu: Menu) -> Dict[str, Any]:
        """将菜单对象转换为字典"""
        return {
            'id': menu.id,
            'name': menu.name,
            'title': menu.title,
            'path': menu.path,
            'component': menu.component,
            'redirect': menu.redirect,
            'icon': menu.icon,
            'type': menu.type,
            'visible': menu.visible,
            'status': menu.status,
            'cache': menu.cache,
            'affix': menu.affix,
            'parent_id': menu.parent_id,
            'sort': menu.sort,
            'permission_code': menu.permission_code,
            'permission_id': menu.permission_id,
            'is_frame': menu.is_frame,
            'external_link': menu.external_link,
            'remark': menu.remark,
            'created_at': menu.created_at.isoformat() if menu.created_at else None,
            'updated_at': menu.updated_at.isoformat() if menu.updated_at else None
        }

    async def _has_circular_reference(self, db: AsyncSession, menu_id: int, parent_id: int, tenant_id: str) -> bool:
        """检查是否存在循环引用"""
        current_parent_id = parent_id
        max_depth = 10  # 防止无限循环

        for _ in range(max_depth):
            if current_parent_id is None or current_parent_id == 0:
                return False
            if current_parent_id == menu_id:
                return True

            parent_menu = await self.get_by_id(db, current_parent_id, tenant_id)
            if not parent_menu:
                return False

            current_parent_id = parent_menu.parent_id

        return False

    async def count(self, db: AsyncSession, query: MenuQuery, tenant_id: str) -> int:
        """获取菜单总数"""
        stmt = select(func.count(Menu.id)).where(Menu.tenant_id == tenant_id)

        if query.name:
            stmt = stmt.where(Menu.name.like(f"%{query.name}%"))
        if query.title:
            stmt = stmt.where(Menu.title.like(f"%{query.title}%"))
        if query.type is not None:
            stmt = stmt.where(Menu.type == query.type)
        if query.status is not None:
            stmt = stmt.where(Menu.status == query.status)
        if query.visible is not None:
            stmt = stmt.where(Menu.visible == query.visible)

        result = await db.execute(stmt)
        return result.scalar()

    async def update_sort(self, db: AsyncSession, menu_id: int, sort: int, tenant_id: str) -> bool:
        """更新菜单排序"""
        menu = await self.get_by_id(db, menu_id, tenant_id)
        if not menu:
            raise BusinessException("菜单不存在")

        menu.sort = sort
        await db.commit()
        return True

    async def change_status(self, db: AsyncSession, menu_id: int, status: bool, tenant_id: str) -> bool:
        """修改菜单状态"""
        menu = await self.get_by_id(db, menu_id, tenant_id)
        if not menu:
            raise BusinessException("菜单不存在")

        menu.status = status
        await db.commit()
        return True

    async def bind_permission(self, db: AsyncSession, menu_id: int, permission_id: int, tenant_id: str) -> bool:
        """绑定菜单权限"""
        menu = await self.get_by_id(db, menu_id, tenant_id)
        if not menu:
            raise BusinessException("菜单不存在")

        # 检查权限是否存在
        from app.services.permission import permission_service
        permission = await permission_service.get_by_id(db, permission_id, tenant_id)
        if not permission:
            raise BusinessException("权限不存在")

        menu.permission_id = permission_id
        menu.permission_code = permission.code
        await db.commit()
        return True

    async def unbind_permission(self, db: AsyncSession, menu_id: int, tenant_id: str) -> bool:
        """解绑菜单权限"""
        menu = await self.get_by_id(db, menu_id, tenant_id)
        if not menu:
            raise BusinessException("菜单不存在")

        menu.permission_id = None
        menu.permission_code = None
        await db.commit()
        return True

    async def get_menu_permissions(self, db: AsyncSession, tenant_id: str) -> List[Dict[str, Any]]:
        """获取所有菜单及其权限绑定信息"""
        result = await db.execute(
            select(Menu)
            .where(Menu.tenant_id == tenant_id)
            .options(selectinload(Menu.permission))
            .order_by(Menu.sort, Menu.id)
        )
        menus = result.scalars().all()

        menu_permissions = []
        for menu in menus:
            menu_data = self._menu_to_dict(menu)
            if menu.permission:
                menu_data['permission'] = {
                    'id': menu.permission.id,
                    'name': menu.permission.name,
                    'code': menu.permission.code,
                    'type': menu.permission.type
                }
            menu_permissions.append(menu_data)

        return menu_permissions


# 创建全局实例
menu_service = MenuService()